import { Layers, Vec3, Node, Collider, Quat, geometry, Mat3 } from "cc";
import { IGridPerception } from "./IGridPerception";
import { GridSensorBase, ProcessCollidersMethod } from "./GridSensorBase";

export class BoxOverlapChecker implements IGridPerception {
    private _cellScale: Vec3 = new Vec3(1, 0.01, 1);
    private _gridSize: Vec3 = new Vec3(16, 1, 16);
    private _rotateWithAgent: boolean;
    private _colliderMask: Layers.BitMask;
    private _centerObject: Node = null!;
    private _agentGameObject: Node = null!;
    private _detectableTags: string[];
    private _initialColliderBufferSize: number;
    private _maxColliderBufferSize: number;

    private _numCells: number;
    private _halfCellScale: Vec3;
    private _cellCenterOffset: Vec3;
    private _cellLocalPositions: Vec3[] = null!;

    private _colliderBuffer: Collider[];

    public gridOverlapDetectedAll: ((gameObject: Node, cellIndex: number) => void) | null = null;
    public gridOverlapDetectedClosest: ((gameObject: Node, cellIndex: number) => void) | null = null;
    public gridOverlapDetectedDebug: ((gameObject: Node, cellIndex: number) => void) | null = null;

    constructor(
        cellScale: Vec3,
        gridSize: Vec3,
        rotateWithAgent: boolean,
        colliderMask: Layers.BitMask,
        centerObject: Node,
        agentGameObject: Node,
        detectableTags: string[],
        initialColliderBufferSize: number,
        maxColliderBufferSize: number
    ) {
        this._gridSize = gridSize;
        this._rotateWithAgent = rotateWithAgent;
        this._colliderMask = colliderMask;
        this._centerObject = centerObject;
        this._agentGameObject = agentGameObject;
        this._detectableTags = detectableTags;
        this._initialColliderBufferSize = initialColliderBufferSize;
        this._maxColliderBufferSize = maxColliderBufferSize;
        this._numCells = gridSize.x * gridSize.z;
        this._halfCellScale = new Vec3(cellScale.x / 2, cellScale.y, cellScale.z / 2);
        this._cellCenterOffset = new Vec3((gridSize.x - 1) / 2, 0, (gridSize.z - 1) / 2);

        this._colliderBuffer = new Array(Math.min(this._maxColliderBufferSize, this._initialColliderBufferSize));

        this._initCellLocalPositions();
    }


    public get rotateWithAgent(): boolean {
        return this._rotateWithAgent;
    }

    public set rotateWithAgent(value: boolean) {
        this._rotateWithAgent = value;
    }

    public get colliderMask(): number {
        return this._colliderMask;
    }

    public set colliderMask(value: number) {
        this._colliderMask = value;
    }

    private _initCellLocalPositions(): void {
        this._cellLocalPositions = new Array(this._numCells);
        for (let i = 0; i < this._numCells; i++) {
            this._cellLocalPositions[i] = this.getCellLocalPosition(i);
        }
    }

    getCellLocalPosition(cellIndex: number): Vec3 {
        const x = (cellIndex / this._gridSize.z - this._cellCenterOffset.x) * this._cellScale.x;
        const z = (cellIndex % this._gridSize.z - this._cellCenterOffset.z) * this._cellScale.z;
        return new Vec3(x, 0, z);
    }

    getCellGlobalPosition(cellIndex: number): Vec3 {
        // if (this._rotateWithAgent) {
        //     return this._centerObject.convert .TransformPoint(this._cellLocalPositions[cellIndex]);
        // } else {
        return this._cellLocalPositions[cellIndex].add(this._centerObject.getWorldPosition());
        // }
    }

    getGridRotation(): Quat {
        // return this._rotateWithAgent ? this._centerObject.transform.rotation : Quat.identity;
        return Quat.IDENTITY;
    }

    public perceive(): void {
        for (let cellIndex = 0; cellIndex < this._numCells; cellIndex++) {
            const cellCenter = this.getCellGlobalPosition(cellIndex);
            const numFound = this._bufferResizingOverlapBoxNonAlloc(cellCenter, this._halfCellScale, this.getGridRotation());
            if (this.gridOverlapDetectedAll) {
                this._parseCollidersAll(this._colliderBuffer, numFound, cellIndex, cellCenter, this.gridOverlapDetectedAll);
            }
            if (this.gridOverlapDetectedClosest) {
                this._parseCollidersClosest(this._colliderBuffer, numFound, cellIndex, cellCenter, this.gridOverlapDetectedClosest);
            }
        }
    }

    public updateGizmo(): void {
        for (let cellIndex = 0; cellIndex < this._numCells; cellIndex++) {
            const cellCenter = this.getCellGlobalPosition(cellIndex);
            const numFound = this._bufferResizingOverlapBoxNonAlloc(cellCenter, this._halfCellScale, this.getGridRotation());
            if (this.gridOverlapDetectedDebug)
                this._parseCollidersClosest(this._colliderBuffer, numFound, cellIndex, cellCenter, this.gridOverlapDetectedDebug);
        }
    }

    private _bufferResizingOverlapBoxNonAlloc(cellCenter: Vec3, halfCellScale: Vec3, rotation: Quat): number {
        // let numFound: number;
        // // eslint-disable-next-line no-constant-condition
        // while (true) {
        //     PhysicsSystem.instance
        //     numFound = Physics.OverlapBoxNonAlloc(cellCenter, halfCellScale, this._colliderBuffer, rotation, this._colliderMask);
        //     if (numFound === this._colliderBuffer.length && this._colliderBuffer.length < this._maxColliderBufferSize) {
        //         this._colliderBuffer = new Array(Math.min(this._maxColliderBufferSize, this._colliderBuffer.length * 2));
        //         this._initialColliderBufferSize = this._colliderBuffer.length;
        //     } else {
        //         break;
        //     }
        // }
        // return numFound;
        return 0
    }

    /**
     * OverlapBoxNonAlloc finds colliders intersecting the given box, and adds them to the provided buffer.
     * @param center The center of the box.
     * @param halfExtents Half the size of the box in each dimension.
     * @param colliders The buffer to store the results in.
     * @param rotation The rotation of the box.
     * @param layerMask A Layer mask that is used to selectively ignore colliders when casting a capsule.
     * @returns Returns the number of colliders stored in the buffer.
     */
    overlapBoxNonAlloc(center: Vec3, halfExtents: Vec3, colliders: Collider[], rotation: Quat, layerMask: number): Collider[] {
        const mat = new Mat3();
        Mat3.fromQuat(mat, rotation);

        const results = new Array<Collider>();

        const obb = geometry.OBB.create(center.x, center.y, center.z,
            halfExtents.x, halfExtents.y, halfExtents.z,
            mat.m00, mat.m01, mat.m02,
            mat.m03, mat.m04, mat.m05,
            mat.m06, mat.m07, mat.m08);

        for (let i = 0; i < colliders.length; i++) {
            const element = colliders[i];
            const distance = geometry.intersect.resolve(obb, element);
            if (distance === 0) {
                continue;
            }
            results.push(element);
        }
        return results;
    }

    private _parseCollidersClosest(foundColliders: Collider[], numFound: number, cellIndex: number, cellCenter: Vec3, detectedAction: (gameObject: Node, cellIndex: number) => void): void {
        // let closestColliderGo: Node = null;
        // let minDistanceSquared = Number.MAX_VALUE;
        // for (let i = 0; i < numFound; i++) {
        //     const currentColliderGo = foundColliders[i].node;
        //     if (currentColliderGo === this._agentGameObject) {
        //         continue;
        //     }

        //     const closestColliderPoint = foundColliders[i].closestPointOnBounds(cellCenter);
        //     const currentDistanceSquared = closestColliderPoint.sub(this._centerObject.transform.position).sqrMagnitude;
        //     if (currentDistanceSquared >= minDistanceSquared) {
        //         continue;
        //     }

        //     let index = -1;
        //     for (let ii = 0; ii < this._detectableTags.length; ii++) {
        //         if (currentColliderGo.CompareTag(this._detectableTags[ii])) {
        //             index = ii;
        //             break;
        //         }
        //     }
        //     if (index > -1 && currentDistanceSquared < minDistanceSquared) {
        //         minDistanceSquared = currentDistanceSquared;
        //         closestColliderGo = currentColliderGo;
        //     }
        // }
        // if (closestColliderGo !== null) {
        //     detectedAction(closestColliderGo, cellIndex);
        // }
    }

    private _parseCollidersAll(foundColliders: Collider[], numFound: number, cellIndex: number, cellCenter: Vec3, detectedAction: (gameObject: Node, cellIndex: number) => void): void {
        for (let i = 0; i < numFound; i++) {
            const currentColliderGo = foundColliders[i].node;
            if (currentColliderGo !== this._agentGameObject) {
                detectedAction(currentColliderGo, cellIndex);
            }
        }
    }

    public registerSensor(sensor: GridSensorBase): void {
        if (sensor.getProcessCollidersMethod() === ProcessCollidersMethod.ProcessAllColliders) {
            this.gridOverlapDetectedAll = sensor.processDetectedObject.bind(sensor);
        } else {
            this.gridOverlapDetectedClosest = sensor.processDetectedObject.bind(sensor);
        }
    }

    public registerDebugSensor(debugSensor: GridSensorBase): void {
        this.gridOverlapDetectedDebug = debugSensor.processDetectedObject.bind(debugSensor);
    }
}